package cachee

import (
	"context"
	"encoding/json"
	"time"

	"github.com/coocood/freecache"
	"github.com/go-redis/redis/v8"
	"github.com/karlseguin/ccache/v2"
	"github.com/vmihailenco/msgpack/v5"
)

type KvStore[K comparable, V any] interface {
	Get(ctx context.Context, key K) (v V, exist bool, err error)
	Set(ctx context.Context, key K, value V, expiration time.Duration) error
	Del(ctx context.Context, key K) error
}

type FreeCacheKvStore[V any] struct {
	Cache *freecache.Cache
	Enc   Encoder[V]
}

var _ KvStore[string, string] = (*FreeCacheKvStore[string])(nil)

func NewFreeCacheKvStore[V any](fc *freecache.Cache, enc Encoder[V]) *FreeCacheKvStore[V] {
	return &FreeCacheKvStore[V]{fc, enc}
}

func (fc *FreeCacheKvStore[V]) Get(ctx context.Context, key string) (v V, exist bool, err error) {
	k := ([]byte)(key)
	value, err := fc.Cache.Get(k)
	if err == freecache.ErrNotFound {
		return v, false, nil
	}

	if err != nil {
		return v, false, err
	}

	v, err = fc.Enc.Decode(value)
	return v, true, err
}

func (fc *FreeCacheKvStore[V]) Set(ctx context.Context, key string, value V, expiration time.Duration) error {
	k := ([]byte)(key)
	v, err := fc.Enc.Encode(value)
	if err != nil {
		return err
	}

	return fc.Cache.Set(k, v, int(expiration.Seconds()))
}

func (fc *FreeCacheKvStore[V]) Del(ctx context.Context, key string) error {
	k := ([]byte)(key)
	_ = fc.Cache.Del(k)
	return nil
}

type CCacheKvStore[V any] struct {
	Cache *ccache.Cache
}

var _ KvStore[string, string] = (*CCacheKvStore[string])(nil)

func NewCCacheKvStore[V any](cc *ccache.Cache) *CCacheKvStore[V] {
	return &CCacheKvStore[V]{cc}
}

func (cc *CCacheKvStore[V]) Get(ctx context.Context, key string) (v V, exist bool, err error) {
	item := cc.Cache.Get(key)
	if item == nil {
		return v, false, nil
	}

	return item.Value().(V), true, nil
}

func (cc *CCacheKvStore[V]) Set(ctx context.Context, key string, value V, expiration time.Duration) error {
	cc.Cache.Set(key, value, expiration)
	return nil
}

func (cc *CCacheKvStore[V]) Del(ctx context.Context, key string) error {
	_ = cc.Cache.Delete(key)
	return nil
}

var _ KvStore[string, string] = (*RedisKvStore[string])(nil)

type RedisKvStore[V any] struct {
	Client *redis.Client
	Enc    Encoder[V]
}

func NewRedisKvStore[V any](c *redis.Client, enc Encoder[V]) *RedisKvStore[V] {
	return &RedisKvStore[V]{c, enc}
}

func (r *RedisKvStore[V]) Get(ctx context.Context, key string) (v V, exist bool, err error) {
	value, err := r.Client.Get(ctx, key).Bytes()
	if err == redis.Nil {
		return v, false, nil
	}

	if err != nil {
		return v, false, err
	}

	v, err = r.Enc.Decode(value)
	return v, true, err
}

func (r *RedisKvStore[V]) Set(ctx context.Context, key string, value V, expiration time.Duration) error {
	v, err := r.Enc.Encode(value)
	if err != nil {
		return err
	}

	return r.Client.Set(ctx, key, v, expiration).Err()
}

func (r *RedisKvStore[V]) Del(ctx context.Context, key string) error {
	return r.Client.Del(ctx, key).Err()
}

type Encoder[V any] struct {
	Encode func(V) ([]byte, error)
	Decode func(data []byte) (V, error)
}

func JsonEncoder[V any]() Encoder[V] {
	return Encoder[V]{
		Encode: func(v V) ([]byte, error) { return json.Marshal(v) },
		Decode: func(data []byte) (v V, err error) { err = json.Unmarshal(data, &v); return },
	}
}

func MsgPackEncoder[V any]() Encoder[V] {
	return Encoder[V]{
		Encode: func(v V) ([]byte, error) { return msgpack.Marshal(v) },
		Decode: func(data []byte) (v V, err error) { err = msgpack.Unmarshal(data, &v); return },
	}
}
